package edu.uwm.cs552;

import java.awt.Color;
import java.awt.Graphics;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;

import edu.uwm.cs552.gfx.SVGImageCreator;
import edu.uwm.cs552.util.FormatException;

/**
 * The fixed part of a hexagonal game board.
 * We have a back image and terrain for certain coordinates.
 * <em>We don't handle rivers or other boundaries yet.</em>
 */
public class HexBoard extends Observable implements TopographyBoard {
	private List<HasTopography<? extends Topography<?>>> features; 
	private SVGImageCreator background;
	private double naturalWidth, naturalHeight;
	private boolean showHidden;
	private boolean drawMesh;
	
	public static final int MIN_SCALE = 10;
	public static final int DEFAULT_SCALE = 20;
	private double scale;
	
	/**
	 * Create a hex board with the given background image
	 * and natural width and height (in terms of hexagon widths).
	 * @param im background image, may be null
	 * @param w width
	 * @param h height
	 */
	public HexBoard(SVGImageCreator im, double w, double h) {
		background = im;
		naturalWidth = w;
		naturalHeight = h;
		features = new ArrayList<HasTopography<? extends Topography<?>>>();
	}
	
	public List<HasTopography<? extends Topography<?>>> getFeatures() {
		return features;
	}
	
	private static final double SQRT32 = Math.sqrt(3)/2; // ##
	
	/* (non-Javadoc)
	 * @see edu.uwm.cs552.CoordBoard#draw(java.awt.Graphics, double, boolean)
	 */
	@Override
	public void draw(Graphics g) {
		// #(
		if (background != null) {
			background.draw(g, getWidth(), getHeight());
		}
		
		for (HasTopography<? extends Topography<?>> f : features)
		{
			f.draw(g, scale, showHidden);
		}
		
		if (drawMesh) {
			g.setColor(Color.black);
			int abound = (int)Math.ceil(naturalWidth);
			int bbound = (int)Math.ceil(naturalHeight/SQRT32);
			for (int b = 0; b <= bbound; ++b) {
				for (int a = 0; a <= abound; ++a) {
					HexCoordinate h = new HexCoordinate(a+b/2,b);
					g.drawPolygon(h.toPolygon(scale));
				}
			}
		}
	}
	
	public <T extends Topography<T>> void putFeature(HasTopography<T> feature) {
		features.remove(feature);
		this.setChanged();
		if (feature.getTopography() != Terrain.TERASE && feature.getTopography() != Barrier.BERASE) {
			features.add(feature);
			this.notifyObservers(feature);
		}
		else {
			this.notifyObservers();
		}
	}
	
	public void setShowHidden(boolean b) {
		showHidden = b;
		this.setChanged();
		this.notifyObservers();
	}
	
	public void setDrawMesh(boolean b) {
		drawMesh = b;
		this.setChanged();
		this.notifyObservers();
	}

	/* (non-Javadoc)
	 * @see edu.uwm.cs552.CoordBoard#getHeight(double)
	 */
	@Override
	public int getHeight() {
		return (int)(naturalHeight*scale);
	}

	/* (non-Javadoc)
	 * @see edu.uwm.cs552.CoordBoard#getWidth(double)
	 */
	@Override
	public int getWidth() {
		return (int)(naturalWidth*scale);
	}
	
	public double getScale() {
		return scale;
	}
	
	public boolean getShowHidden() {
		return showHidden;
	}
	
	public void setScale(double s) {
		if (s <= 0) throw new IllegalArgumentException("scale must be positive: " + s);
		if (s < MIN_SCALE) s = MIN_SCALE;
		scale = s;
		this.setChanged();
		this.notifyObservers();
	}

	/* (non-Javadoc)
	 * @see edu.uwm.cs552.CoordBoard#write(java.io.PrintWriter)
	 */
	@Override
	public void write(PrintWriter pw) {
		// #(
		if (background == null) {
			pw.println();
		} else {
			pw.println(background.getURL().toExternalForm());
		}
		pw.println(naturalWidth);
		pw.println(naturalHeight);
		
		Topography<?> previousTopography = null;
		for (HasTopography<? extends Topography<?>> f : features)
		{
			Topography<?> t = f.getTopography();
			if (t == Barrier.BERASE || t == Terrain.TERASE) continue;
			if (t != previousTopography) {
				previousTopography = t;
				pw.println(t);
			}
			pw.println(f);
		}
	}
	
	/* (non-Javadoc)
	 * @see edu.uwm.cs552.CoordBoard#read(java.io.BufferedReader)
	 */
	@Override
	public void read(BufferedReader br) throws IOException, FormatException, NumberFormatException {
		// #(
		String url = br.readLine();
		naturalWidth = Double.parseDouble(br.readLine());
		naturalHeight = Double.parseDouble(br.readLine());
		Topography<?> previousTopography = null;
		String s;
		background = url.equals("") ? null : new SVGImageCreator(new URL(url));
		features.clear();
		while ((s = br.readLine()) != null) {
			if (!s.startsWith("<")) {
				boolean terrainFailed = false;
				boolean barrierFailed = false;
				FormatException e = null;
				try {
					previousTopography = Terrain.valueOf(s);
				} catch (IllegalArgumentException ex) {
					terrainFailed = true;
					e = new FormatException(ex);
				}
				try {
					previousTopography = Barrier.valueOf(s);
				} catch (IllegalArgumentException ex) {
					barrierFailed = true;
					e = new FormatException(ex);
				}
				
				if (terrainFailed && barrierFailed)
					throw e;
			} else {
				if (previousTopography instanceof Terrain) {
					HexCoordinate h = HexCoordinate.fromString(s);
					h.setTopography((Terrain)previousTopography);
					features.add(h);
				}
				else if (previousTopography instanceof Barrier) {
					HexEdge h = HexEdge.fromString(s);
					h.setTopography((Barrier)previousTopography);
					features.add(h);
				}
			}
		}
		
		this.setChanged();
		this.notifyObservers();
	}
}
